#include "handle.h"

int sendn(int fd, const void *buf, size_t n) {
    size_t left = n; // 剩余需要发送的字节数
    const char *p = (const char *)buf; // 指向待发送数据的指针
    while(left > 0) { 
        ssize_t sent = send(fd, p, left, 0); // 尝试发送剩余数据
        if(sent <= 0) // 发送出错或者连接关闭
        {
            return -1;
        } 
        left -= sent; // 更新剩余的字节数
        p += sent; // 移动指针到未发送的数据位置
    }
    return 0; // 走到这说明成功发送所有数据
}

int recvn(int fd, void *buf, size_t n) {
    size_t left = n; // 剩余需要接收的字节数
    char *p = (char *)buf; // 指向接收缓冲区的指针
    while(left > 0) {
        ssize_t recvd = recv(fd, p, left, 0); // 尝试接收剩余数据
        if(recvd <= 0)  // 接收出错或者连接关闭
        {
            return -1; 
        }
        left -= recvd; // 更新剩余的字节数
        p += recvd; // 移动指针到缓冲区未填充的位置
    } 
    return 0; // 走到这说明成功接收所有数据
}


// 发送TLV数据包
// type:TLV包的类型标识
// data:要发送的数据指针
// length:要发送的数据长度
int send_tlv(int fd, int type, const void *data, size_t length) {
    // 创建TLV头
    tlv_t header;
    header.type = htonl(type);      // 将主机字节序转换为网络字节序(大端)
    header.length = htonl(length);
    
    // 先发送TLV头 type和length
    if(sendn(fd, &header, sizeof(header))) {
        return -1;
    }
    
    // 如果有数据则发送数据
    if(length > 0 && data != NULL) {
        if(sendn(fd, data, length)) {
            return -1;
        }
    }
    
    return 0;
}

// 接收TLV数据包
// type 输出参数，接收到的TLV类型
// buf 数据接收缓冲区
// length 输入时为缓冲区大小，输出时为实际接收的数据长度
int recv_tlv(int fd, int *type, void *buf, size_t *length) {
    tlv_t header;
    
    // 接收TLV头 type和length
    if(recvn(fd, &header, sizeof(header))) {
        return -1;
    }
    
    // 获取真实的类型和数据长度
    // nthol 将网络字节序转换回主机字节序
    *type = ntohl(header.type);
    // data_len存放从TLV头部解析出的Value部分的长度，也就是实际接收的数据长度。
    size_t data_len = ntohl(header.length); 
    
    // 检查缓冲区是否足够存放数据value，也就是发来的数据，防止缓冲区溢出
    if(buf != NULL && *length < data_len) {
        return -1;
    }
    
    // 接收数据
    if(data_len > 0) {
        // 使用recvn可以保证全部接收完成
        if(recvn(fd, buf, data_len)) {
            return -1;
        }
    }
    
    // 将实际接收的数据长度返回给调用者
    *length = data_len;
    return 0;
}

int upload_file(int sockfd, const char *filename, int resume) {
    // 检查本地是否有要上传的文件
    struct stat buf;
    int ret = stat(filename,&buf);
    if(ret == -1)
    {
        printf("not find this file\n");
        return -1;
    }

    // 发送文件名到服务端
    // 发送文件名TLV包  type类型是文件名
    if(send_tlv(sockfd, TYPE_FILENAME, filename, strlen(filename))) {
        perror("send filename");
        return -1;
    }

    // 获取文件信息 主要是获取文件大小 .st_size
    struct stat statbuf;
    if(stat(filename, &statbuf)) {
        perror("stat");
        return -1;
    }
    
    // **********************************************************************
    // 断点续传处理
    off_t offset = 0;
    // 接收偏移量TLV
    int type;  // 声明type变量
    size_t len = sizeof(offset);
    // 接收服务端返回的已上传偏移量offset
    if(recv_tlv(sockfd, &type, &offset, &len)) {
        perror("recv offset");
        return -1;
    }
    if(offset > 0) // 如果偏移量offset > 0，转为断点续传
    {
        resume = CMD_UPLOAD_RESUME;
    }
    
    // 断点续传
    if (resume) {
        // 发送文件总大小TLV type是文件大小类型
        if(send_tlv(sockfd, TYPE_FILESIZE, &statbuf.st_size, sizeof(statbuf.st_size))) {
            perror("send file size");
            return -1;
        }
        // 接收偏移量TLV
        int type;  // 声明type变量
        size_t len = sizeof(offset);
        // 接收服务端返回的已上传偏移量offset
        if(recv_tlv(sockfd, &type, &offset, &len)) {
            perror("recv offset");
            return -1;
        }    
        printf("Resuming upload from offset: %ld\n", (long)offset);
    
        // 从头开始传
    } else {
        // 普通模式发送文件大小TLV  htobe64是主机字节序转64位大端
        off_t net_filesize = htobe64(statbuf.st_size);
        // 正常从头开始发送文件，type是文件大小
        if(send_tlv(sockfd, TYPE_FILESIZE, &net_filesize, sizeof(net_filesize))) {
            perror("send filesize");
            return -1;
        }
    }
    
    // if(resume == 1)->Resuming upload of  else->Uploading
    printf("%s %s (size: %ld bytes)\n", 
           resume ? "Resuming upload of" : "Uploading", 
           filename, (long)statbuf.st_size);

    // 打开文件
    int fd = open(filename, O_RDONLY);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    // ********************************使用mmap*********************************
    void *map = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if(map == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return -1;
    }

    // 定位到断点位置
    const char *data_ptr = (const char *)map + offset;
    size_t remaining = statbuf.st_size - offset;

    // 分块发送（保持与原来类似的进度显示）
    off_t total_sent = offset;
    const size_t chunk_size = 4096; // 保持与原来相同的块大小
    
    while(remaining > 0) {
        size_t to_send = remaining > chunk_size ? chunk_size : remaining;
        
        if(send_tlv(sockfd, TYPE_FILEDATA, data_ptr, to_send)) {
            perror("send file data");
            break;
        }
        
        data_ptr += to_send;
        remaining -= to_send;
        total_sent += to_send;
        
        printf("\rProgress: %.2f%%", (double)total_sent/statbuf.st_size*100);
        fflush(stdout);
    }

    munmap(map, statbuf.st_size);
    close(fd);

    // 验证文件完整性
    if(total_sent != statbuf.st_size) {
        printf("\nUpload incomplete: %ld/%ld bytes\n", (long)total_sent, (long)statbuf.st_size);
        return -1;
    }
    
    printf("\nUpload completed!\n");
    return 0;
}

int download_file(int sockfd, const char *filename, int resume) {
    // 发送文件名TLV
    if(send_tlv(sockfd, TYPE_FILENAME, filename, strlen(filename))) {
        perror("send filename");
        return -1;
    }

    // 断点续传处理
    off_t offset = 0;
    struct stat statbuf;
    // 检查本地已下载文件的大小，如果已经下载一部分了，就直接断点续传
    if (stat(filename, &statbuf) == 0) {
        offset = statbuf.st_size; 
    }
    if(offset > 0) {
        resume = CMD_DOWNLOAD_RESUME;
    }

    if (resume) {
                
        // 发送已下载的偏移量TLV
        if(send_tlv(sockfd, TYPE_OFFSET, &offset, sizeof(offset))) {
            perror("send resume offset");
            return -1;
        }
    }

    // 接收文件大小TLV
    off_t net_filesize;
    size_t len = sizeof(net_filesize);
    int type;
    // 接收服务器返回的文件总大小
    if(recv_tlv(sockfd, &type, &net_filesize, &len)) {
        perror("recv filesize");
        return -1;
    }
    // 转换网络字节序(大端)为主机字节序(小端)
    off_t filesize = be64toh(net_filesize);
    if(filesize <= 0) {
        fprintf(stderr, "Invalid file size: %ld\n", (long)filesize);
        return -1;
    }
    
    printf("%s %s (size: %ld bytes)\n", 
           resume ? "Resuming download of" : "Downloading", 
           filename, (long)filesize);
    
    // 打开本地文件
    int flags = O_WRONLY | O_CREAT;
    if (resume && offset > 0) {
        flags |= O_APPEND; // 如果是断点续传，就从已下载的位置后面开始追加的写
        printf("Resuming from offset: %ld\n", (long)offset);
    } else {
        flags |= O_TRUNC; // 如果是普通模式或者偏移量为0，那就从头开始写
                          // 截断模式会清空已有的文件
    }
    
    int fd = open(filename, flags, 0666);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 接收文件数据
    char buf[4096];
    off_t total_recv = offset;
    while(total_recv < filesize) {
        // 这里不能用strlen，因为strlen遇到\0就结束，而文件中间部分可能有\0
        // 这里len的含义是表示最多能接受的数据量，而不是buf中有这么多有效数据。
        len = sizeof(buf);
        // recv_tlv会通过length参数返回实际接收的数据长度，赋值给传入传出参数len
        if(recv_tlv(sockfd, &type, buf, &len)) {
            perror("recv file data");
            break;
        }
        // send是发送网络数据，不能写入磁盘文件
        if(write(fd, buf, len) != (ssize_t)len) { // 到了这里，len中放的就是实际数据长度
            perror("write");
            break;
        }
        
        total_recv += len;
        printf("\rProgress: %.2f%%", (double)total_recv/filesize*100);
        fflush(stdout);
    }
    
    close(fd);
    if(total_recv != filesize) {
        printf("\nDownload incomplete: %ld/%ld bytes\n", (long)total_recv, (long)filesize);
        return -1;
    }
    
    printf("\nDownload completed!\n");
    return 0;
}

int delete_file(int sockfd, const char *filename) {
    // 发送文件名TLV
    if(send_tlv(sockfd, TYPE_FILENAME, filename, strlen(filename))) {
        perror("send filename");
        return -1;
    }

    // 接收状态TLV
    int status;
    // 把状态status当做长度传过去，如果传回来的数据实际长度是0，表示删除完毕 
    size_t len = sizeof(status); 
    int type;
    if(recv_tlv(sockfd, &type, &status, &len)) {
        perror("recv status");
        return -1;
    }

    if(status == 0) {
        printf("File %s deleted successfully\n", filename);
    } else {
        printf("Delete failed for file %s\n", filename);
    }

    return status;
}

int handle_command(int sockfd, char *command, char *file_name) {
    
    if(file_name == NULL || strlen(file_name) == 0) {
        fprintf(stderr, "Filename required for command %s\n", command);
        return EXIT_FAILURE;
    }

    // 解析命令
    int cmd;
    int resume = 0;
    
    if(strcmp(command, "puts") == 0) {
        cmd = CMD_UPLOAD;
    } 
    else if(strcmp(command, "gets") == 0) {
        cmd = CMD_DOWNLOAD;
    }
    else if(strcmp(command, "rm") == 0) {
        cmd = CMD_DELETE;
    }
     else {
        fprintf(stderr, "Invalid command: %s\n", command);
        return EXIT_FAILURE;
    }

    // 发送命令TLV
    if(send_tlv(sockfd, TYPE_COMMAND, &cmd, sizeof(cmd))) {
        perror("send command");
        close(sockfd);
        return EXIT_FAILURE;
    }

    // 执行对应操作
    int result;
    switch(cmd) {
        case CMD_UPLOAD:
        case CMD_UPLOAD_RESUME:
            result = upload_file(sockfd, file_name, resume);
            break;
        case CMD_DOWNLOAD:
        case CMD_DOWNLOAD_RESUME:
            result = download_file(sockfd, file_name, resume);
            break;
        case CMD_DELETE:
            result = delete_file(sockfd, file_name);
            break;
    }

    close(sockfd);
    return result ? EXIT_FAILURE : EXIT_SUCCESS;
}

